﻿using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning
{
	public class EvoPCA2 : EvoPCA
	{
		public const int DEFAULT_THRESHOLD = 10;

		public int IndSize { get; protected set; }
		public float FactorThreshold { get; set; }
		public float ReferenceSumVariance { get; set; }

		public EvoPCA2()
		{
			FactorThreshold = DEFAULT_THRESHOLD;
			ReferenceSumVariance = 1;
		}

		public override void  Evaluate(List<AbstractIndividual> popul, FitnessFunction fitFunc, out Individual best)
		{
			if (fitFunc is OutputsVarianceObjFunction)
			{	// enable resampling if possible.
				((OutputsVarianceObjFunction) fitFunc).DataAmount = DataAmount;
			}

 			base.Evaluate(popul, fitFunc, out best);

			// compute mean variances.
			var vars = new List<float[]>();
			foreach (var ind in popul)
			{
				vars.Add(ind.Fitness.Extra.ToArray());
			}
			var means = VectorMath.MeanVector(vars);

			var size = means.Length;
			IndSize = size;
			for (int i = size-1; i > 0; i--)
			{
				var factor = means[0] / means[i];
				if (factor > FactorThreshold)
				{
					RemoveLastNode();
					IndSize--;	// decrease indviduals size.

					// change best individual as well.
					var wCount = NeuralNetwork.InputsNumber;
					best.Genes.RemoveRange(best.Genes.Count - wCount - 1, wCount);
					best.Fitness.Extra.RemoveAt(best.Fitness.Extra.Count - 1);
				}
				else
				{
					break;
				}
			}
		}

		public override void CollectStats()
		{
			base.CollectStats();

			// add information about individual's size.
			stats[CurrentGeneration - 1].Data.Insert(0,IndSize);

			// add information about sum variance rates.
			var delim = 1f/ReferenceSumVariance;
			var tempSumVar = 0f;
			foreach (var ind in Population)
			{
				var sumVar = VectorMath.Sum(ind.Fitness.Extra.ToArray());
				tempSumVar += sumVar*delim;
			}
			stats[CurrentGeneration - 1].Data.Insert(1, tempSumVar / Population.Count);
		}

		/// <summary>
		/// Removes weights correspondent to the last node in the network for all individuals in population.
		/// </summary>
		public void RemoveLastNode ()
		{
			var wCount = NeuralNetwork.InputsNumber;
			var size = NeuralNetwork.GetTotalConnectionsNumber();
			if (NeuralNetwork.UseBias) ++wCount;

			// modify current and selected individuals.
			foreach (Individual ind in Population)
			{
				ind.Genes.RemoveRange(size - wCount - 1, wCount);
				ind.Fitness.Extra.RemoveAt(ind.Fitness.Extra.Count - 1);
			}
			foreach (Individual ind in selPopul)
			{
				ind.Genes.RemoveRange(size - wCount - 1, wCount);
				ind.Fitness.Extra.RemoveAt(ind.Fitness.Extra.Count - 1);
			}

			// change ANN structure.
			var props = new NeuralNetProperties(NeuralNetwork);

			// decrease nodes number.
			props.nodesNumber[NeuralNetwork.Layers.Count - 1]--;
			NeuralNetwork = LayeredNeuralNetwork.CreateNetwork(props);

			((NEObjFunction) FitnessFunction).Network = NeuralNetwork;

			// update and reevaluate current best individual.
			if (bestInd != null)
			{
				bestInd.Genes.RemoveRange(bestInd.Genes.Count - wCount - 1, wCount);
				bestInd.Fitness.Extra.RemoveAt(bestInd.Fitness.Extra.Count - 1);

				bestInd.Fitness = FitnessFunction.Compute(bestInd.Genes.ToArray());
			}
		}
	}
}
